#!/usr/bin/env python
# -*- coding: utf-8 -*-

from authlib.jose import jwt
from authlib.jose.errors import ExpiredTokenError, InvalidTokenError
from config.settings import settings
import threading
from hashlib import md5
from datetime import datetime
import time
import logging

Lock = threading.Lock()

FORMAT = "%(asctime)s %(thread)d %(message)s"
logging.basicConfig(level=logging.DEBUG, format=FORMAT)

"""
API接口安全
1.时间戳校验
2.签名校验
3.token校验
"""


class APISecurity(object):
    """
    功能：验证API安全
    """

    __instance = None

    def __init__(self):
        pass

    def __new__(cls, *args, **kwargs):
        if not cls.__instance:
            try:
                Lock.acquire()
                # double check
                if not cls.__instance:
                    cls.__instance = super(APISecurity, cls).__new__(cls, *args, **kwargs)
            finally:
                Lock.release()
        return cls.__instance

    def _params_filter(self, params, delimiter='&', charset='utf-8', excludes=['sign', 'sign_type']):
        """过滤不参与签名运算的参数"""
        ks = list(params.keys())
        ks.sort()
        newparams = {}
        prestr = ''
        if params.get('input_charset', False):
            charset = params['input_charset']
        for k in ks:
            v = params[k]
            if k not in excludes:
                newparams[k] = v
                prestr += '%s=%s%s' % (k, newparams[k], delimiter)
        prestr = prestr[:-1]
        return newparams, prestr

    def _build_mysign(self, prestr, key=None, sign_type='MD5'):
        """生成签名"""
        if sign_type == 'MD5':
            prestr += '&key=%s' % str(key)
            return md5(prestr.encode(encoding='utf-8')).hexdigest().lower()
        return ''

    def _verify_sign(self, params, salt=None):
        """验证签名"""
        _, prestr = self._params_filter(params, excludes=['sign'])
        mysign = self._build_mysign(prestr, key=salt)
        if mysign != params.get('sign'):
            logging.error('签名{}校验失败。'.format(params.get('sign')))
            return False
        else:
            return True

    def _verify_timestamp(self, params):
        """ 校验时间戳，150秒内有效
        @pram req_timestamp str,int: 请求参数中的时间戳(10位)
        """
        if len(str(params.get('timestamp'))) == 10:
            req_timestamp = int(params.get('timestamp'))
            now_time = datetime.now()
            now_timestamp = int(time.mktime(now_time.timetuple()))
            if req_timestamp <= now_timestamp + 150:
                return True
        logging.error('请求时间戳{}与服务器时间戳{}校验失败。'.format(params.get('timestamp'), now_timestamp))
        return False

    def verify_public(self, params):
        """ 校验请求是否有效
        @param req_params dict: 请求的所有查询参数(公共参数和私有参数)
        """
        res = dict(code="FAIL", msg=None)
        try:
            app_id = params["app_id"]
            nonce_str = params["nonce_str"]
            timestamp = params["timestamp"]
            sign = params["sign"]
        except KeyError as e:
            logging.error(str(e))
            res.update(msg="Invalid public params")
        except Exception as e:
            logging.error(str(e))
            res.update(msg="Unknown server error")
        else:
            if self._verify_timestamp(params):
                # 根据appid查询appsecret作为salt，appsecret不在网络上传输
                if self._verify_sign(params, settings.APP_SECRET):
                    res.update(code="SUCCESS", msg="Verification Pass.")
                else:
                    res.update(msg="Invalid query string")
            else:
                res.update(msg="Invalid timestamp")
        return res

    def verify_access_token(self, token):
        """验证token"""
        try:
            try:
                claims = jwt.decode(token, settings.SECRET_KEY)
                claims.validate()  # 验证不通过将抛异常
                return claims
            except ExpiredTokenError as e:
                logging.error(f'Token error: {e}')
                return {'error': 'Token expired'}
            except InvalidTokenError as e:
                logging.error(f'Token error: {e}')
                return {'error': 'Token invalid'}
        except Exception as e:
            logging.error(f'Unexpected error: {e}')
            return {'error': 'Internal server error'}
